package com.jzo2o.orders.manager.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.db.DbRuntimeException;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.customer.AddressBookApi;
import com.jzo2o.api.customer.dto.response.AddressBookResDTO;
import com.jzo2o.api.foundations.ServeApi;
import com.jzo2o.api.foundations.dto.response.ServeAggregationResDTO;
import com.jzo2o.api.market.CouponApi;
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
import com.jzo2o.api.trade.NativePayApi;
import com.jzo2o.api.trade.TradingApi;
import com.jzo2o.api.trade.dto.request.NativePayReqDTO;
import com.jzo2o.api.trade.dto.response.NativePayResDTO;
import com.jzo2o.api.trade.dto.response.TradingResDTO;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.api.trade.enums.TradingStateEnum;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.common.utils.DateUtils;
import com.jzo2o.common.utils.ObjectUtils;
import com.jzo2o.common.utils.StringUtils;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.orders.base.config.OrderStateMachine;
import com.jzo2o.orders.base.constants.RedisConstants;
import com.jzo2o.orders.base.enums.OrderPayStatusEnum;
import com.jzo2o.orders.base.enums.OrderStatusChangeEventEnum;
import com.jzo2o.orders.base.enums.OrderStatusEnum;
import com.jzo2o.orders.base.mapper.OrdersMapper;
import com.jzo2o.orders.base.model.domain.Orders;
import com.jzo2o.orders.base.model.dto.OrderSnapshotDTO;
import com.jzo2o.orders.manager.model.dto.request.OrdersPayReqDTO;
import com.jzo2o.orders.manager.model.dto.request.PlaceOrderReqDTO;
import com.jzo2o.orders.manager.model.dto.response.OrdersPayResDTO;
import com.jzo2o.orders.manager.model.dto.response.PlaceOrderResDTO;
import com.jzo2o.orders.manager.porperties.TradeProperties;
import com.jzo2o.orders.manager.service.IOrdersCreateService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;

/**
 * <p>
 * 下单服务类
 * </p>
 *
 * @author itcast
 * @since 2023-07-10
 */
@Slf4j
@Service
public class OrdersCreateServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersCreateService {
    @Autowired
    private ServeApi serveApi;
    @Autowired
    private AddressBookApi addressBookApi;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private OrdersCreateServiceImpl ordersCreateService;
    @Autowired
    private NativePayApi nativePayApi;
    @Autowired
    private TradeProperties tradeProperties;
    @Autowired
    private TradingApi tradeApi;
    @Autowired
    private CouponApi couponApi;
    @Autowired
    private OrderStateMachine orderStateMachine;

    @Override
    public List<AvailableCouponsResDTO> getAvailableCoupons(Long serveId, Integer purNum) {
        // 1.获取服务
        ServeAggregationResDTO serveResDTO = serveApi.findById(serveId);
        if (serveResDTO == null || serveResDTO.getSaleStatus() != 2) {
            throw new ForbiddenOperationException("服务不可用");
        }

        // 2.计算订单总金额
        BigDecimal totalAmount = serveResDTO.getPrice().multiply(new BigDecimal(purNum));

        // 3.获取可用优惠券,并返回优惠券列表
        List<AvailableCouponsResDTO> available = couponApi.getAvailable(totalAmount);
        return available;
    }

    /**
     * 获取支付结果
     *
     * @param id
     * @return
     */
    @Override
    public OrdersPayResDTO getPayResultFromTradServer(Long id) {
        //查询订单表是否存在
        Orders orders = this.getById(id);
        if (ObjectUtils.isEmpty(orders)) {
            throw new CommonException("订单不存在");
        }
        // 如果是待支付 且 有支付服务交易单号  需要调用微信查询状态
        if (orders.getPayStatus() == 2 && ObjectUtil.isNotEmpty(orders.getTradingOrderNo())) {
            //调用微信查询
            TradingResDTO tradingResDTO = tradeApi.findTradResultByTradingOrderNo(orders.getTradingOrderNo());
            //如果订单结果不为空 且 支付成功 这里更新订单状态
            if (ObjectUtils.isNotEmpty(tradingResDTO) && ObjectUtils.equals(tradingResDTO.getTradingState(), TradingStateEnum.YJS)) {
                // 修改订单状态和支付状态
                OrderSnapshotDTO orderSnapshotDTO = OrderSnapshotDTO.builder()
                        .payTime(LocalDateTime.now())
                        .tradingOrderNo(tradingResDTO.getTradingOrderNo())
                        .tradingChannel(tradingResDTO.getTradingChannel())
                        .thirdOrderId(tradingResDTO.getTransactionId())
                        .build();
                orderStateMachine.changeStatus(String.valueOf(orders.getId()), OrderStatusChangeEventEnum.PAYED, orderSnapshotDTO);
            }
        }
        //返回结果
        //返回结果
        Orders newOrders = this.getById(id);//查询最新数据
        OrdersPayResDTO ordersPayResDTO = new OrdersPayResDTO();
        ordersPayResDTO.setPayStatus(newOrders.getPayStatus());
        ordersPayResDTO.setProductOrderNo(newOrders.getId());
        ordersPayResDTO.setTradingOrderNo(newOrders.getTradingOrderNo());
        ordersPayResDTO.setTradingChannel(newOrders.getTradingChannel());
        return ordersPayResDTO;
    }

    /**
     * 支付
     *
     * @param id
     * @param ordersPayReqDTO
     * @return
     */
    @Override
    public OrdersPayResDTO pay(Long id, OrdersPayReqDTO ordersPayReqDTO) {
        //支付方法实现思路：
        Orders orders = this.getById(id);
        //- 根据订单号查询订单是否存在，如果不存在，直接返回错误提示
        if (ObjectUtils.isEmpty(orders)) {
            throw new CommonException("订单不存在");
        }
        //- 订单如果存在，判断订单是否已完成支付，如果已完成支付，直接返回错误提示
        if (StringUtils.isNotEmpty(orders.getTransactionId()) || orders.getPayStatus() == 4) {
            throw new CommonException("订单已支付");
        }
        //- 如果订单存在且未支付，使用nativePayApi远程调用创建支付记录，获取支付二维码地址，返回前端
        NativePayReqDTO nativePayDTO = new NativePayReqDTO();
        nativePayDTO.setTradingChannel(ordersPayReqDTO.getTradingChannel());//支付渠道
        if (ObjectUtil.equal(ordersPayReqDTO.getTradingChannel(), PayChannelEnum.ALI_PAY)) {
            nativePayDTO.setEnterpriseId(tradeProperties.getAliEnterpriseId());//阿里商户号
        } else {
            nativePayDTO.setEnterpriseId(tradeProperties.getWechatEnterpriseId());//微信商户号
        }
        if (StringUtils.isNotBlank(orders.getTradingChannel()) &&
                !StringUtils.equals(orders.getTradingChannel(), ordersPayReqDTO.getTradingChannel().toString())) {
            nativePayDTO.setChangeChannel(true);//是否切换频道
        } else {
            nativePayDTO.setChangeChannel(false);//是否切换频道
        }
        nativePayDTO.setProductAppId("jzo2o.orders");//业务系统标识
        nativePayDTO.setProductOrderNo(id);//业务系统订单号
        nativePayDTO.setTradingAmount(orders.getRealPayAmount());//交易金额
        nativePayDTO.setMemo(orders.getServeItemName());//备注
        NativePayResDTO nativePayResDTO = nativePayApi.createDownLineTrading(nativePayDTO);
        //- 更新订单中的部分字段信息
        OrdersPayResDTO ordersPayResDTO = BeanUtil.copyProperties(nativePayResDTO, OrdersPayResDTO.class);
        ordersPayResDTO.setPayStatus(0);//待支付
        //5. 更新订单信息
        this.lambdaUpdate()
                .eq(Orders::getId, nativePayResDTO.getProductOrderNo())
                .set(Orders::getTradingOrderNo, nativePayResDTO.getTradingOrderNo())//交易系统订单号【对于三方来说：商户订单】
                .set(Orders::getTradingChannel, nativePayResDTO.getTradingChannel().toString())//支付渠道【支付宝、微信、现金、免单挂账】
                .update();
        return ordersPayResDTO;
    }

    @Override
    public PlaceOrderResDTO placeOrder(PlaceOrderReqDTO dto) {
        //查寻服务信息，判断是否存在且为已上架状态
        ServeAggregationResDTO serve = serveApi.findById(dto.getServeId());
        if (ObjectUtils.isEmpty(serve) || ObjectUtils.notEqual(serve.getSaleStatus(), 2)) {
            throw new CommonException("服务不存在");
        }
        //服务存在且上架，则判断该地区是否存在
        AddressBookResDTO address = addressBookApi.detail(dto.getAddressBookId());
        if (ObjectUtils.isEmpty(address))
            throw new CommonException("服务地区不存在");
        //赋值保存
        Orders orders = new Orders();
        orders.setId(generateOrderId());//自动生成 19位：2位年+2位月+2位日+13位序号
        orders.setUserId(UserContext.currentUserId());//下单用户id token
        orders.setServeId(dto.getServeId());//服务id 前端传入
        //下面几个需要调用运营微服务查询获取
        orders.setServeTypeId(serve.getServeTypeId());//服务类型id
        orders.setServeTypeName(serve.getServeTypeName());//服务类型名称
        orders.setServeItemId(serve.getServeItemId());//服务项目id
        orders.setServeItemName(serve.getServeItemName());//服务项目名称
        orders.setServeItemImg(serve.getServeItemImg());//服务项目图片
        orders.setUnit(serve.getUnit());//服务单位
        orders.setPrice(serve.getPrice());//价格
        orders.setCityCode(serve.getCityCode());//城市编码
        //状态
        orders.setOrdersStatus(0);//订单状态 设置为未支付
        orders.setPayStatus(2);//支付状态 设置为未支付
        //金额
        orders.setPurNum(dto.getPurNum());//前端传入
        orders.setTotalAmount(serve.getPrice().multiply(new BigDecimal(dto.getPurNum())));//总额: 价格 * 数量
        orders.setDiscountAmount(new BigDecimal("0"));// 优惠金额 当前默认0
        orders.setRealPayAmount(orders.getTotalAmount().subtract(orders.getDiscountAmount()));// 实付金额 订单总金额 - 优惠金额

        //地址: 调用客户微服务, 根据地址id查询
        String serveAddress = address.getProvince() + address.getCity() + address.getCounty() + address.getAddress();
        orders.setServeAddress(serveAddress);//服务详细地址
        orders.setContactsPhone(address.getPhone());//联系人手机号
        orders.setContactsName(address.getName());//联系人名称
        orders.setLon(address.getLon());//经度
        orders.setLat(address.getLat());//维度
        orders.setServeStartTime(dto.getServeStartTime());//服务开始时间
        orders.setSortBy(DateUtils.toEpochMilli(orders.getServeStartTime()) + orders.getId() % 100000);//排序
        //保存订单
        if (ObjectUtils.isNull(dto.getCouponId())) {
            //无优惠券下单
            ordersCreateService.saveOrder(orders);
        } else {
            // 使用优惠券
            ordersCreateService.saveOrdersWithCoupon(orders, dto.getCouponId());
        }
        //4. 返回
        return new PlaceOrderResDTO(orders.getId());
    }

    @GlobalTransactional
    public void saveOrdersWithCoupon(Orders orders, Long couponId) {
        CouponUseReqDTO couponUseReqDTO = new CouponUseReqDTO();
        couponUseReqDTO.setOrdersId(orders.getId());
        couponUseReqDTO.setId(couponId);
        couponUseReqDTO.setTotalAmount(orders.getTotalAmount());

        //1. 调用优惠券微服务优惠券核销
        CouponUseResDTO couponUseResDTO = couponApi.use(couponUseReqDTO);

        //2. 修改订单的优惠优惠金额和实付金额
        // 设置优惠金额
        orders.setDiscountAmount(couponUseResDTO.getDiscountAmount());
        // 计算实付金额
        BigDecimal realPayAmount = orders.getTotalAmount().subtract(orders.getDiscountAmount());
        orders.setRealPayAmount(realPayAmount);
        //保存订单
        boolean save = this.save(orders);

        if (!save) {
            throw new DbRuntimeException("下单失败");
        }
        //构建快照对象
        OrderSnapshotDTO orderSnapshotDTO = BeanUtil.toBean(baseMapper.selectById(orders.getId()), OrderSnapshotDTO.class);
        //状态机启动
        orderStateMachine.start(null, String.valueOf(orders.getId()), orderSnapshotDTO);
    }

    @Transactional(rollbackFor = MyBatisSystemException.class)
    public void saveOrder(Orders orders) {
        boolean save = this.save(orders); //1s
        if (!save) {
            throw new DbRuntimeException("下单失败");
        }
        //构建快照对象
        OrderSnapshotDTO orderSnapshotDTO = BeanUtil.toBean(baseMapper.selectById(orders.getId()), OrderSnapshotDTO.class);
        //状态机启动
        orderStateMachine.start(null, String.valueOf(orders.getId()), orderSnapshotDTO);
    }

    @Override
    public Long generateOrderId() {
        Long increment = redisTemplate.opsForValue().increment(RedisConstants.Lock.ORDERS_SHARD_KEY_ID_GENERATOR, 1);
        long id = DateUtils.getFormatDate(LocalDateTime.now(), "yyMMdd") * 10000000000000L + increment;
        return id;
    }


}
